package com.zxcl.config;

import cn.hutool.core.lang.UUID;
import com.alibaba.fastjson2.JSON;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.zxcl.constant.AuthConstant;
import com.zxcl.constant.BusinessEnum;
import com.zxcl.constant.HttpConstant;
import com.zxcl.model.LoginResult;
import com.zxcl.model.Result;
import com.zxcl.service.impl.UserDetailsServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.security.authentication.AccountExpiredException;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.InternalAuthenticationServiceException;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;

import java.io.PrintWriter;
import java.time.Duration;

/**
 * 安全配置类
 */
@Configuration
@Slf4j
public class AuthSecurityConfig extends WebSecurityConfigurerAdapter {
    @Autowired
    private UserDetailsServiceImpl userDetailsService;

    @Autowired
    private StringRedisTemplate redisTemplate;

    /**
     * 告诉框架我们要走的登录逻辑，这里就是把我们写的用户详细新服务告诉框架
     * AuthenticationManagerBuilder:身份验证管理器构建器
     */
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        //userDetailsService:用户详细信息服务
        auth.userDetailsService(userDetailsService);
    }

    //在提供的代码片段中，http是HttpSecurity类的实例，后者是Java中的Spring Security框架的一部分。Configure(HttpSecurity Http)方法是用于为应用程序自定义安全配置的重写方法。
    //
    //让我们分解这两个方法调用：
    //
    //：此方法调用用于禁用Spring Security中的跨站点请求伪造(CSRF)保护。默认情况下，Spring Security包含CSRF保护，以防止恶意攻击。但是，在某些情况下，您可能希望禁用它，例如在开发无状态API时，或者如果您有其他安全措施来防止CSRF攻击。
    //
    //Http.cors().Disable()；：此方法调用用于禁用Spring Security中的跨域资源共享(CORS)。CORS是Web浏览器强制实施的一种安全机制，用于限制跨域的HTTP请求。默认情况下，Spring Security包含CORS支持，但在某些场景下，如果您的CORS配置不同，或者您的应用程序不需要跨域请求，则可能需要禁用该功能。
    //
    //因此，这两个方法调用分别用于在您的Spring Security应用程序的HttpSecurity配置中禁用CSRF保护和CORS支持。
    /**
     * 网络请求的配置
     *
     * @param http
     * @throws Exception
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        //关闭跨站请求伪造的拦截
        http.csrf().disable();
        http.cors().disable();
        //禁用session  Spring Security永远不会创建HttpSession，也永远不会使用它来获取
        http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
        //配置登录信息  这段配置是表单提交，所以对于用户名和密码的提交一定是从body中过来的
        http.formLogin()
                .loginProcessingUrl(AuthConstant.LOGIN_URL)  //这里应该描述为默认修饰符可以再同个模块中访问
                .successHandler(authenticationSuccessHandler()) //注入登录成功的处理器
                .failureHandler(authenticationFailureHandler()) //注入登录失败的处理器
                .permitAll();
        // 登出的配置
        http.logout()
                .logoutUrl(AuthConstant.LOGOUT_URL)
                .logoutSuccessHandler(logoutSuccessHandler())
                .permitAll();  //等效于调用permitAll（true）
        // 其它所有接口 都必须认证后才可以访问 （必须在 securityContext 中有认证对象 才可以访问）
        http.authorizeRequests()
                .anyRequest()
                .authenticated();

    }

    /**
     * 登录成功的处理器
     * 生成一个token
     * 当前的认证对象
     * 存入redis
     * 返回token
     *
     * @return 响应一个json并把token带出
     */
    @Bean
    public AuthenticationSuccessHandler authenticationSuccessHandler() {
        return (request, response, authentication) -> {
            response.setContentType(HttpConstant.APPLICATION_JSON);
            response.setCharacterEncoding(HttpConstant.UTF_8);
            //uuid 36位中间有-
            String token = UUID.randomUUID().toString().replace("-", "");
            //  getPrincipal获取身份
            String userStr = JSON.toJSONString(authentication.getPrincipal());
            //把用户的的json放到redis，并设置过期时间
            redisTemplate.opsForValue().set(AuthConstant.LOGIN_TOKEN_PREFIX + token, userStr, Duration.ofSeconds(AuthConstant.TOKEN_TIME));
            // token返回 {code:200,msg:ok,data:{accessToken:token,expireIn:7200}}
            LoginResult loginResult = new LoginResult(token, AuthConstant.TOKEN_TIME);
            Result<LoginResult> result = Result.success(loginResult);
            ObjectMapper objectMapper = new ObjectMapper();
            String s = objectMapper.writeValueAsString(result);
            PrintWriter writer = response.getWriter();
            writer.write(s);
            writer.flush();
            writer.close();
        };
    }

    /**
     * 登录失败的处理器
     *
     * @return
     */
    @Bean
    public AuthenticationFailureHandler authenticationFailureHandler() {
        return (request, response, exception) -> {
            log.error("登录失败:{}", exception.getMessage());
            response.setContentType(HttpConstant.APPLICATION_JSON);
            response.setCharacterEncoding(HttpConstant.UTF_8);
            Result<String> result = new Result<>();
            result.setCode(BusinessEnum.UN_AUTHORIZATION.getCode());
            if (exception instanceof BadCredentialsException) {
                result.setMsg("用户名或者密码错误");
            } else if (exception instanceof UsernameNotFoundException) {
                result.setMsg("用户名错误");
            } else if (exception instanceof AccountExpiredException) {
                result.setMsg("账号异常，请联系管理员");
            } else if (exception instanceof InternalAuthenticationServiceException) {
                result.setMsg(exception.getMessage());
            } else {
                result.setMsg(BusinessEnum.UN_AUTHORIZATION.getDesc());
            }
            ObjectMapper objectMapper = new ObjectMapper();
            String s = objectMapper.writeValueAsString(result);
            PrintWriter writer = response.getWriter();
            writer.write(s);
            writer.flush();
            writer.close();
        };
    }

    /**
     * 登出的处理器
     * 根据token把redis中的数据删除
     *
     * @return
     */
    @Bean
    public LogoutSuccessHandler logoutSuccessHandler() {
        return (request, response, authentication) -> {
            response.setContentType(HttpConstant.APPLICATION_JSON);
            response.setCharacterEncoding(HttpConstant.UTF_8);
            //取出用户传过来的token
            String authorization = request.getHeader(AuthConstant.AUTHORIZATION);
            String token = authorization.replaceFirst(AuthConstant.BEARER, "");
            redisTemplate.delete(AuthConstant.LOGIN_TOKEN_PREFIX + token);
            Result<Object> result = Result.success(null);
            ObjectMapper objectMapper = new ObjectMapper();
            String s = objectMapper.writeValueAsString(result);
            PrintWriter writer = response.getWriter();
            writer.write(s);
            writer.flush();
            writer.close();
        };
    }


    //密码匹配器
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

}
